using System.Linq;
#if NETFX_CORE
using System.ComponentModel;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#else
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
#endif

namespace FreshMeat.Xaml
{
   /// <summary>
   /// An attached behavior which allows a button to close the window to which it belongs.
   /// The window's DialogResult will be set to the value specified by the <see cref="ResultProperty"/>.
   /// If the button's <see cref="Button.Command"/> is not bound to anything and <see cref="ResultProperty"/>
   /// is true (making it an "ok"-type button rather than a "cancel"-type button) and if the 
   /// window's DataContext implements <see cref="INotifyDataErrorInfo"/> then the button will
   /// automatically be disabled as long as <see cref="INotifyDataErrorInfo.HasErrors"/> is true.
   /// See <see cref="OpenWindowBehavior"/> for how the result can be used to make your life easier.
   /// </summary>
   /// <remarks>
   /// For example, this button would close the window it's in and to set the return value to "True":
   ///    &lt;Button Content="Close" Xaml:OpenWindowBehavior.Close="True" /&gt;
   /// </remarks>
   public class CloseWindowBehavior : AttachedBehavior<CloseWindowBehavior, Button>
   {
      /// <summary>
      /// The value returned by the XChildWindow.
      /// </summary>
      public readonly static DependencyProperty ResultProperty = DependencyProperty.RegisterAttached(
         "Result",
         typeof(bool?),
         typeof(CloseWindowBehavior),
         new FrameworkPropertyMetadata((bool?)null, (d, e) => GetInstance(d)));

      public static bool? GetResult(DependencyObject d)
      {
         return (bool?)d.GetValue(ResultProperty);
      }

      public static void SetResult(DependencyObject d, bool? value)
      {
         d.SetValue(ResultProperty, value);
      }

      protected override void OnAttached()
      {
         var associatedObject = TryGetAssociatedObject();
         associatedObject.Click += OnAssociatedObjectClick;

         // The visual tree is not always complete at this point
         // so we wait until later in the lifecycle to check for the notifier.
         associatedObject.LayoutUpdated += AssociatedObjectReady;
      }

#if NETFX_CORE
      void AssociatedObjectReady(object sender, object e)
#else
      void AssociatedObjectReady(object sender, System.EventArgs e)
#endif
      {
         var associatedObject = TryGetAssociatedObject();
         if (associatedObject == null)
            return;

         associatedObject.LayoutUpdated -= AssociatedObjectReady;

         // Now that the visual tree is more likely to be complete,
         // check for a notifier.
         var notifier = TryGetNotifier(associatedObject);
         if (notifier != null)
            notifier.ErrorsChanged += OnErrorsChanged;
      }

      protected override void OnDetaching()
      {
         var associatedObject = TryGetAssociatedObject();
         if (associatedObject == null)
            return;

         associatedObject.Click -= OnAssociatedObjectClick;

         var notifier = TryGetNotifier(associatedObject);
         if (notifier != null)
            notifier.ErrorsChanged -= OnErrorsChanged;
      }

      void OnAssociatedObjectClick(object sender, RoutedEventArgs e)
      {
         var associatedObject = TryGetAssociatedObject();
         if (associatedObject == null)
            return;

         var result = GetResult(associatedObject);

         var window = TryGetWindow(associatedObject);
         if (window != null)
            window.DialogResult = result;
      }

      // Called whenever the window's model says there's been a change in data validation errors
      void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e)
      {
         var associatedObject = TryGetAssociatedObject();
         if (associatedObject == null)
            return;

         var notifier = sender as INotifyDataErrorInfo;
         if (notifier == null)
            return;

         // Only enable the button when there are no errors
         associatedObject.IsEnabled = !notifier.HasErrors;
      }

      INotifyDataErrorInfo TryGetNotifier(Button associatedObject)
      {
         // If this button sets the dialog result to false then we're not interested in disabling it.
         // We only disable the button if it returns true which makes it the "ok"-type button.
         if (!GetResult(associatedObject).GetValueOrDefault())
            return null;

         // If button is bound to a command we let that handle enabling/disabling the button
         if (associatedObject.Command != null)
            return null;

         // Try to get the window's data context
         var window = TryGetWindow(associatedObject);
         if (window == null || window.DataContext == null)
            return null;

         // See if it implements the error notification interface
         var notifier = window.DataContext as INotifyDataErrorInfo;
         if (notifier == null)
            return null;

         // Got it!
         return notifier;
      }

      XChildWindow TryGetWindow(Button associatedObject)
      {
         var window = VisualTreeEnumerator.EnumerateUp(associatedObject).OfType<XChildWindow>().FirstOrDefault();
         return window;
      }
   }
}